Function.prototype.bind = function() {
    if (arguments.length < 2 && arguments[0] === undefined) {
        return this;
    }
    var thisObj = this,
        args = Array.prototype.slice.call(arguments),
        obj = args.shift();
    return function() {
        return thisObj.apply(obj, args.concat(Array.prototype.slice.call(arguments)));
    };
};
Function.bind = function() {
    var args = Array.prototype.slice.call(arguments);
    return Function.prototype.bind.apply(args.shift(), args);
}

function Gallery(preferences) {
    this.url = preferences.url;
    this.containerWidth = preferences.containerWidth;
    this.boxCount = preferences.boxCount;
    this.boxMargin = preferences.boxMargin;
    this.swipeEnabled = preferences.swipeEnabled;
    this.scrollbarWidth = preferences.scrollbarWidth;
    this.scrollbarColor = preferences.scrollbarColor;
    this.showCaption = preferences.showCaption;
    this.showDescription = preferences.showDescription;
    this.gutter = 10;
    this.isScrolling = false;
    
    if (this.swipeEnabled) {
	this.boxSize = (4 * this.boxMargin) + (4 * ((this.containerWidth - 6 * this.boxCount * this.boxMargin) / (4 * this.boxCount + 2)));
    }
    else {
	this.boxSize = (4 * this.boxMargin) + (4 * ((this.containerWidth - 6 * this.boxCount * this.boxMargin - this.scrollbarWidth - this.gutter) / (4 * this.boxCount + 2)));
    }
};

Gallery.prototype.itemsCount = 0;
Gallery.prototype.imageCount = 0;
Gallery.prototype.imageSrcs = [];
Gallery.prototype.images = [];
Gallery.prototype.captions = [];
Gallery.prototype.descriptions = [];
Gallery.prototype.infoImages = [];

Gallery.prototype.didShow = function() {
    //Read data from spreadsheet.
    this._getData = function(json) {
	if (json) {
	    this.itemsCount = json.rows.length;
	    
	    for (var row = 0; row < json.rows.length; row++) {
		this.imageSrcs.push(json.rows[row].c[0].v);
		this.captions.push(json.rows[row].c[1].v);
		this.descriptions.push(json.rows[row].c[2].v);
	    }
	}
	
	this.processImages();
    }.bind(this);
    
    getData(this.url, 0, this._getData, 2);
}
Gallery.prototype.processImages = function() {
    this.loadInfoImage();
}
//Need to create one Image object per info button.
Gallery.prototype.loadInfoImage = function() {
    var self = this;
    var img = new Image();

    img.onload = function() {
	self.infoImages.push(img);
	self.loadImage(self.imageSrcs.shift());
    }
    
    img.src = "http://risegadgets.googlecode.com/svn/test/Sponsors/info.png";
}
Gallery.prototype.loadImage = function(imageSrc) {
    var self = this;
    var img = new Image();

    img.onload = function() {
	self.images.push(img);
	
	if (self.imageSrcs.length == 0) {
	    self.construct();
	}
	else {
	    self.processImages();
	}
    }
    
    img.src = imageSrc;
}
Gallery.prototype.construct = function() {
    var $container = $("#container");
    var padding;
    
    if (self.swipeEnabled) {
	padding = (this.containerWidth - 6 * this.boxCount * this.boxMargin) / (4 * this.boxCount + 2);
    }
    else {	//Account for scrollbar and scrollbar gutter.
	padding = (this.containerWidth - 6 * this.boxCount * this.boxMargin - this.scrollbarWidth - this.gutter) / (4 * this.boxCount + 2);
    }
    
    console.log(padding);
    console.log(this.boxSize);
    
    //Build UI.
    for (var i = 0; i < this.itemsCount; i++) {
	var $dissolve = $("<div>");
	var $image = $("<div>");
	var $imageInfo = $("<div>");
	var $info = $("<div>");
	var $scroll = $("<div>");
	var $description = $("<div>");
	
	$dissolve.css({
	    "width" : this.boxSize + "px",
	    "height" : this.boxSize + "px",
	    "margin": this.boxMargin + "px"
	});
	
	/* Need to add additional margins to boxes because jScrollPane has issues with padding/margins on the scrolling div.
	Using margins/padding on the scrolling div will cause the scrollbars to be pushed outside of the placeholder.
	The other problem is that the padding or margin at the bottom is never shown, no matter if it's set on the scrolling div or on the jspPane.
	To get around this, we need to set a wider margin:
	    On the left side of the boxes in the first column.
	    On the right side of the boxes in the last column.
	    At the top of the boxes in the first row.
	    At the bottom of the boxes in the last row. */
	
	//First row
	if (i < this.boxCount) {
	    $dissolve.css("marginTop", padding + this.boxMargin + "px");
	}
	
	//First column
	if (i  % this.boxCount == 0) {
	    $dissolve.css("marginLeft", padding + this.boxMargin + "px");
	}
	
	//Last column
	if ((i + 1) % this.boxCount == 0) {
	    $dissolve.css("marginRight", padding + this.boxMargin + "px");
	}
	
	//Last row
	if (i == (this.itemsCount - 1)) {
	    $dissolve.css("marginBottom", padding + this.boxMargin + "px");
	}
	
	//Image
	$image.addClass("opaque");
	$imageInfo.addClass("imageInfo");
	
	$image.append($("<div>").addClass("logo").append(this.images[i]));
	
	if (this.showCaption) {
	    $imageInfo.html(this.captions[i]).addClass("caption");
	}
    
	$image.append($imageInfo);
	
	//Description
	$info.addClass("transparent");
	$scroll.addClass("scroll");
	$description.html(this.descriptions[i]).addClass("description");
	
	$scroll.append($description);
	$info.append($scroll);
	
	//Put it all together.
	$dissolve.addClass("dissolve");
	$dissolve.append($image);
	$dissolve.append($info);
	$container.append($dissolve);
	
	//Adjust height of the info button to be the same as the height of its containing div.
	if (this.showDescription) {
	    this.infoImages[i].height = $imageInfo.height();
	    $imageInfo.append(this.infoImages[i]);
	}
	
	//Set the line-height of the caption to allow for vertical alignment.
	//$imageInfo.css("line-height", $imageInfo.height() + "px");
	$imageInfo.find("img").css("display", "none");

	//Scale logo.
	this.scaleLogo(i, $image, $imageInfo);
	
	//Scale thumbnail.
	this.scaleThumbnail(i, $info, $scroll, $image);
    }
}
Gallery.prototype.scaleLogo = function(i, $image, $imageInfo) {
    var self = this;
    var maxHeight;
    
    //Account for the height, padding and positioning of the caption.
    if (this.showCaption) {
	maxHeight = this.boxSize - $imageInfo.height() - 5 - 5;
    }
    else {
	maxHeight = this.boxSize;
    }

    //Scale the image to fit inside a box where boxSize is the width and height of the box.
    var settings = {
	url: this.images[i].src,
	rsW: this.boxSize,
	rsH: maxHeight,
	callback: function(newWidth, newHeight) {                                       
	    if (newWidth && newHeight) {
		self.images[i].width = newWidth;
		self.images[i].height = newHeight;
		
		//Change height of div containing image to facilitate vertical alignment.
		$image.find("div:nth-child(1)").height($image.height() - $imageInfo.height() - 5);
		
		//Pull top margin of image back up by half of its height.
		self.images[i].style.marginTop = -(self.images[i].offsetHeight / 2);
	    }
	    
	    self.imageCount++;
	
	    //All images have been scaled.
	    if (self.imageCount == self.itemsCount) {
		self.init();
	    }
	},
	onerror: function(image) {
	    //Do nothing.
	}
    };

    scaleToFit(settings);
}
//Adjust height of the thumbnail to use the width remaining after the description has been placed.
Gallery.prototype.scaleThumbnail = function(i, $info, $scroll, $image) {
    var thumbnail = new Image();
    var maxHeight = $info.height() - $scroll.height();
    var self = this;
    
    thumbnail.src = this.images[i].src;
    //Scale the image to fit inside a box where boxSize is the width and height of the box.
    var settings = {
	url: thumbnail.src,
	rsW: this.boxSize,
	rsH: maxHeight,
	callback: function(newWidth, newHeight) {
	    console.log(thumbnail.width);
	    console.log(thumbnail.height);

	    if (newWidth && newHeight) {
		thumbnail.width = newWidth;
		thumbnail.height = newHeight;
		console.log(thumbnail.width);
		console.log(thumbnail.height);
		$info.prepend(thumbnail);
	    }
	},
	onerror: function(image) {
	    //Do nothing.
	}
    };

    scaleToFit(settings);
}
Gallery.prototype.init = function() {
    var self = this;
    this._isClose = false;
    this.setupListeners();
    
    //Load the scrolling library.
    loadScrollingScript(this.swipeEnabled, function() {
	self.configureScrolling(self);
    });
}
Gallery.prototype.setupListeners = function() {
    var self = this;

    this._clickHandler = function(event) {
	//Check if user is scrolling.
	if (self.swipeEnabled) {
	    if ($("#container").data("dragging")) {
		return;
	    }
	}
	
	if (self._isClose != true) {
	    $(".dissolve").each(function(index) {
		if (event.currentTarget == this) {
		    $(this).addClass("closeup");
		    $(this).find(".imageInfo img").css("display", "block");
		}
		else {
		    $(this).addClass("background");
		}
	    });
	    
	    self._isClose = true;
	}
	else {	//Has closeup class.
	    var closeClicked = false;
	    
	    $(".dissolve").each(function(index) {
		if (event.currentTarget == this) {
		    if ($(this).hasClass("closeup")) {
			if ($(this).find(".scroll").data("dragging")) {
			    self.isScrolling = true;
			}

			if (self.showDescription && !self.isScrolling) {
			    var $image = $(this).find("> div:nth-child(1)");
			    var $info = $(this).find("> div:nth-child(2)");

			    $image.toggleClass("transparent");
			    $image.toggleClass("opaque");
			    $info.toggleClass("transparent");
			    $info.toggleClass("opaque");
			}
			else {
			    self.isScrolling = false;
			}
			
			closeClicked = true;
			return false;
		    }
		}
	    });
	    
	    if (!closeClicked) {	//Background clicked.
		$(".dissolve").each(function(index) {
		    if ($(this).hasClass("closeup")) {
			$(this).find(".imageInfo img").css("display", "none");
			$(this).css("zIndex", 5);
		    
			this._backgroundClicked = function(event) {
			    $(this).css("zIndex", "");
			    this.removeEventListener("webkitTransitionEnd", arguments.callee, false);
			}.bind(this);
		    
			this.addEventListener("webkitTransitionEnd", this._backgroundClicked, false);
			$(this).removeClass("closeup");
		    }
		    else {
			$(this).removeClass("background");
		    }
		});
		
		self._isClose = false;
	    }
	}
	
        arr = null;
    }
    
    $(".dissolve").each(function(index) {
        this.addEventListener("click", self._clickHandler, false);
    });
}
Gallery.prototype.configureScrolling = function(self) {
    if (self.swipeEnabled) {	//Overscroll
	$("#container").overscroll({
	    //thumbsColor: self.scrollbarColor,
	    thumbThickness: 0
	});
	$(".scroll").overscroll({
	    //thumbsColor: self.scrollbarColor,
	    thumbThickness: 0
	});
    }
    else {	//jScrollPane
	$("#container").jScrollPane({
	    horizontalGutter: self.gutter,
	    verticalGutter: self.gutter
	});
	$(".scroll").jScrollPane({
	    horizontalGutter: self.gutter,
	    verticalGutter: self.gutter
	});
	$(".scroll")
	    .bind(
		"jsp-scroll-y",
		function(event, scrollPositionY, isAtTop, isAtBottom)
		{
		    self.isScrolling = true;
		}
	    );
	$(".jspVerticalBar").width(self.scrollbarWidth);
	$(".jspDrag").css("background-color", self.scrollbarColor);
    }

    readyEvent();
}